package cn.kinoko.service.common.export.component;

import cn.kinoko.common.code.ErrorCodeEnum;
import cn.kinoko.common.utils.ExceptionUtil;
import cn.kinoko.common.utils.MD5Utils;
import cn.kinoko.service.common.export.ExportHandler;
import cn.kinoko.service.common.export.ExportTaskStatusEnum;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * @author kk
 */
@Slf4j
@Component
public class ExportManager implements InitializingBean {

    @Resource
    private ExportQueue queue;
    @Lazy
    @Resource
    private Map<Integer, ExportHandler> exportHandlerMap;
    @Resource
    private ExportFileArea fileArea;

    private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(2, Thread.ofVirtual().factory());


    @Override
    public void afterPropertiesSet() {
        // 每5秒执行一次
        executor.scheduleAtFixedRate(executeExportTask(), 0, 5, TimeUnit.SECONDS);
        executor.scheduleAtFixedRate(clearExpiredFile(), 0, 30, TimeUnit.SECONDS);
    }

    /**
     * 提交导出任务
     */
    public void submit(ExportTask task) {
        task.setStatus(ExportTaskStatusEnum.WAITING);
        // 生成任务ID
        task.setTaskId(MD5Utils.encode(task.getUid().toString() + task.getParams()));
        queue.push(task);
    }

    /**
     * 下载文件
     */
    public void download(String taskId, HttpServletResponse response) {
        // 获取任务
        ExportTask task = queue.getTaskByTaskId(taskId);
        ExceptionUtil.isNull(task, ErrorCodeEnum.SYSTEM_ERROR_B0502);
        ExceptionUtil.isNull(!task.getStatus().equals(ExportTaskStatusEnum.FINISHED), ErrorCodeEnum.SYSTEM_ERROR_B0503);

        // 更新状态 - 下载中
        task.setStatus(ExportTaskStatusEnum.DOWNLOADING);
        byte[] file = fileArea.get(task.getTaskId());
        ExceptionUtil.isNull(file, ErrorCodeEnum.SYSTEM_ERROR_B0502);

        // 写入响应body
        response.setCharacterEncoding("utf-8");
        response.setContentType(task.getContentType());
        response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=".concat(String.valueOf(URLEncoder.encode(task.getFileName(), StandardCharsets.UTF_8))));
        try (OutputStream ops = response.getOutputStream()) {
            IOUtils.write(file, ops);
            // 移除任务
            queue.removeByTaskId(taskId);
            fileArea.remove(taskId);
        } catch (IOException e) {
            log.error("[下载文件] 文件写入失败", e);
        }
    }

    /**
     * 执行导出任务
     */
    public Runnable executeExportTask() {
        return () -> {
            ExportTask task = queue.next();
            try {
                // 预估文件区域是否有足够的剩余空间
                if (task != null && fileArea.evaluateMemoryUsed(task.getEvaluateSize())) {
                    ExportHandler handler = exportHandlerMap.get(task.getTopic().getCode());
                    // 更新状态 - 制作中
                    task.setStatus(ExportTaskStatusEnum.BUILDING);
                    // 执行导出
                    byte[] bytes = handler.export(task);
                    // 放入文件区域
                    boolean flag = fileArea.put(task, bytes);
                    if (flag) {
                        // 更新状态 - 已完成
                        task.setStatus(ExportTaskStatusEnum.FINISHED);
                    } else {
                        // 更新状态 - 返回等待状态
                        task.setStatus(ExportTaskStatusEnum.WAITING);
                    }
                }
            } catch (Exception e) {
                // 更新状态 - 失败
                task.setStatus(ExportTaskStatusEnum.FAIL);
                log.error("[导出任务] 失败，taskId:{}, errorMsg:", task.getTaskId(), e);
            }
        };
    }

    /**
     * 清理过期的文件
     */
    public Runnable clearExpiredFile() {
        return () -> {
            // 清理过期的文件
            List<String> expired = fileArea.clearExpired();
            // 清理任务
            expired.forEach(queue::removeByTaskId);
        };
    }

}
